package com.echo.im.server.netty.processor.impl;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempUtil;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSONObject;
import com.echo.im.common.contant.IMConstant;
import com.echo.im.common.contant.IMRedisKey;
import com.echo.im.common.enums.IMCmdType;
import com.echo.im.common.model.IMLoginInfo;
import com.echo.im.common.model.IMSendInfo;
import com.echo.im.common.model.IMSessionInfo;
import com.echo.im.server.netty.IMServerGroup;
import com.echo.im.server.netty.context.ChannelAttrKey;
import com.echo.im.server.netty.context.UserChannelMap;
import com.echo.im.server.netty.processor.AbstractMessageProcessor;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.concurrent.TimeUnit;


/**
 * 1. 校验token
 * 2. 得到用户信息
 * 3. 不允许同设备多源登录
 * 4. 绑定用户信息
 * 5. 上线用户到Redis并绑定和用户和im-server的关系
 * 6. 初始化心跳次数
 * 7. 响应ws
 * @author echo
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LoginProcessor extends AbstractMessageProcessor<IMLoginInfo> {

    @Value("${jwt.accessToken.secret}")
    private String accessTokenSecret;

    private final RedisTemplate<String,Object> redisTemplate;

    @Override
    public void process(ChannelHandlerContext ctx, IMLoginInfo loginInfo) {
        //1. 校验token
        IMSessionInfo sessionInfo;
        try {
            JSONObject jsonObject = (JSONObject)StpUtil.getTokenSessionByToken(loginInfo.getAccessToken()).get(IMConstant.TOKEN_SESSION_NAME);
            sessionInfo = jsonObject.toJavaObject(IMSessionInfo.class);
        }catch (Exception e){
            ctx.close();
            log.warn("用户token校验不通过，强制下线,token:{}", loginInfo.getAccessToken());
            return;
        }
        if (sessionInfo == null){
            ctx.close();
            log.warn("用户token校验不通过，强制下线,token:{}", loginInfo.getAccessToken());
            return;
        }

        // 2. 得到用户信息
        Long userId = sessionInfo.getUserId();
        Integer terminal = sessionInfo.getTerminal();

        // 3. 不允许同设备多源登录
        Channel channel = UserChannelMap.getChannel(userId, terminal);
        if (channel != null && !ctx.channel().id().equals(channel.id())) {
            // 不允许多地登录,强制下线
            IMSendInfo<Object> sendInfo = new IMSendInfo<>();
            sendInfo.setCmd(IMCmdType.FORCE_LOGUT.code());
            sendInfo.setData("您已在其他地方登陆，将被强制下线");
            channel.writeAndFlush(sendInfo);
            log.info("异地登录，强制下线,userId:{}", userId);
        }

        //4. 绑定用户信息
        UserChannelMap.addChannel(userId, terminal, ctx.channel());
        AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
        ctx.channel().attr(userIdAttr).set(userId);
        AttributeKey<Object> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
        ctx.channel().attr(terminalAttr).set(terminal);

        //5. 上线用户到Redis并绑定和用户和im-server的关系,并设置过期时间，可以通过心跳来续期
        //整体机制类似于 Redission 中的watchDog
        String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString());
        redisTemplate.opsForValue().set(key, IMServerGroup.serverId, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);

        //6. 初始化心跳次数
        AttributeKey<Long> heartBeatAttr = AttributeKey.valueOf(ChannelAttrKey.HEARTBEAT_TIMES);
        ctx.channel().attr(heartBeatAttr).set(0L);

        //7. 响应ws
        IMSendInfo<Object> sendInfo = new IMSendInfo<>();
        sendInfo.setCmd(IMCmdType.LOGIN.code());
        ctx.channel().writeAndFlush(sendInfo);
    }

    @Override
    public void process(IMLoginInfo data) {

    }

    @Override
    public IMCmdType getCmdType() {
        return IMCmdType.LOGIN;
    }

    @Override
    public IMLoginInfo transForm(Object o) {
        HashMap map = (HashMap) o;
        return BeanUtil.fillBeanWithMap(map, new IMLoginInfo(), false);
    }
}
